﻿//
// *************************************************************************************
// WP-Framework
// 
// Developed by
// Lukas Kretschmar (lkretschmar.dev@bluewin.ch)
//
// Switzerland, Copyright (c) 2013 lkretschmar
// This content is released under the MIT License (http://opensource.org/licenses/MIT).
//     
// *************************************************************************************
//
//
			

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace KretschIT.WP_Fx.EasyMoq.Generator.Items
{
    internal class TypeFinder : ITypeFinder
    {
        private readonly IClassAnalyzer analyzer = new ClassAnalyzer();

        #region Generics

        public IEnumerable<Type> GetGenericTypeArgumentsOf(Type type)
        {
            return this.FindTypesIn(type, this.AddGenericArgumentTypesOf);
        }

        private void AddGenericArgumentTypesOf(Type type, List<Type> foundTypes)
        {
            this.FindGenericArgumentTypesOf(type, t => t.GetGenericArguments().Where(a => !a.IsGenericParameter), foundTypes);
        }       

        public IEnumerable<Type> GetGenericConstraintTypesOf(Type type)
        {
            return this.FindTypesIn(type, this.AddConstraintTypesOf);
        }

        private void AddConstraintTypesOf(Type type, List<Type> foundTypes)
        {
            if (type.IsGenericType)
            {
                this.FindGenericArgumentTypesOf(type, t => t.GetGenericTypeDefinition().GetGenericArguments().SelectMany(a => a.GetGenericParameterConstraints()).Where(ct => ct != typeof(ValueType)), foundTypes);
            }
        }

        private void FindGenericArgumentTypesOf<TIn>(TIn type, Func<TIn, IEnumerable<Type>> getTypesOf, List<Type> foundTypes)
        {
            var types = getTypesOf(type);
            this.FindNewTypesIn(types, foundTypes);
        }

        #endregion

        #region Events


        public IEnumerable<Type> GetEventTypesOf(Type type)
        {
            return this.FindTypesIn(type, this.AddEventHandlersOf);
        }

        private void AddEventHandlersOf(Type type, List<Type> foundTypes)
        {
            this.AddTypesOf(t => this.analyzer.GetEventsOf(t).Select(e => e.EventHandlerType), type, foundTypes);
        }

        #endregion

        #region Properties

        public IEnumerable<Type> GetPropertyTypesOf(Type type)
        {
            return this.FindTypesIn(type, this.AddPropertyTypesOf);
        }

        private void AddPropertyTypesOf(Type type, List<Type> foundTypes)
        {
            this.AddTypesOf(t => this.analyzer.GetPropertiesOf(t).Select(p => p.PropertyType), type, foundTypes);
        }

        #endregion

        #region Methods

        public IEnumerable<Type> GetMethodTypesOf(Type type)
        {
            return this.FindTypesIn(type, this.AddMethodTypes);
        }

        private void AddMethodTypes(Type type, List<Type> foundTypes)
        {
            var methods = this.analyzer.GetMethodsOf(type);
            foundTypes.AddRange(this.GetMethodReturnTypesOf(methods));
            foundTypes.AddRange(this.GetMethodArgumenTypesOf(methods));
            foundTypes.AddRange(this.GetMethodConstraintTypesOf(methods));
        }

        private IEnumerable<Type> GetMethodReturnTypesOf(IEnumerable<MethodInfo> methods)
        {
            return methods.Where(m => m.ReturnType != typeof(void) && !m.ReturnType.IsGenericParameter).Select(m => UnifyType(m.ReturnType));
        }

        private Type UnifyType(Type type)
        {
            if (type.IsGenericType)
            {
                return type.GetGenericTypeDefinition();
            }
            return type;
        }

        private IEnumerable<Type> GetMethodArgumenTypesOf(IEnumerable<MethodInfo> methods)
        {
            return methods.SelectMany(m => m.GetParameters().Select(p => p.ParameterType).Where(t => !t.IsGenericParameter));
        }

        private IEnumerable<Type> GetMethodConstraintTypesOf(IEnumerable<MethodInfo> methods)
        {
            var constraints = new List<Type>();
            foreach(var method in methods.Where(m => m.IsGenericMethod))
            {
                this.FindGenericArgumentTypesOf(method, m => m.GetGenericMethodDefinition().GetGenericArguments().SelectMany(a => a.GetGenericParameterConstraints()), constraints);
            }
            return constraints;
        }

        #endregion

        #region Common Helpers

        private IEnumerable<Type> FindTypesIn(Type type, Action<Type, List<Type>> findIn)
        {
            var foundTypes = new List<Type>();
            if (type != null)
            {
                findIn(type, foundTypes);
            }
            return foundTypes;
        }

        private void AddTypesOf(Func<Type, IEnumerable<Type>> getSubTypesOf, Type type, List<Type> foundTypes)
        {
            var types = getSubTypesOf(type);
            foreach (var t in types)
            {
                this.AddIfMissing(t, foundTypes);
                this.AddGenericArgumentTypesOf(t, foundTypes);
            }
        }

        private void FindNewTypesIn(IEnumerable<Type> types, List<Type> foundTypes)
        {
            foreach (var t in types)
            {
                this.AddIfMissing(t, foundTypes);
                this.GetSubArgumentsOfGeneric(t, foundTypes);
            }
        }

        private void AddIfMissing(Type type, List<Type> foundTypes)
        {
            var t = type;
            if (t.IsGenericType)
            {
                t = t.GetGenericTypeDefinition();
            }
            if (!foundTypes.Contains(t))
            {
                foundTypes.Add(t);
            }
        }

        private void GetSubArgumentsOfGeneric(Type t, List<Type> foundTypes)
        {
            if (t.IsGenericType)
            {
                this.AddGenericArgumentTypesOf(t, foundTypes);
            }
        }

        #endregion
    }
}
